﻿#region using

using System;
using System.Text;
using System.Xml;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography;
using CipherLib.Cipher;
using CipherLib;

#endregion

namespace CipherLib.Cipher.Xml
{
    /// <summary>
    ///  XML Encryption is a standard way to exchange or store encrypted XML data, 
    ///  without worrying about the data being easily read. For more information about the XML Encryption standard, 
    ///  see the World Wide Web Consortium (W3C) specification for XML Encryption located at http://www.w3.org/TR/xmldsig-core/.
    /// </summary>
    public class CipherXml : CipherBaseLib
    {
        private const string XML_ENVELOPE = AppCommon.EX_XML_CONTENT;

        XmlDocument _xmlDocument = new XmlDocument();

        public XmlDocument XmlDocument
        {
            get { return _xmlDocument; }
        }


        public CipherXml(string subjectName) : base(subjectName) { }

        public CipherXml(string subjectName, StoreName defaultEncryptStoreName, 
            StoreLocation defaultStoreLocation) :
            base(subjectName, defaultEncryptStoreName, defaultStoreLocation) { }

        #region en/decryption

        /// <summary>
        /// 
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public XmlDocument EncryptString(string message)
        {
            XmlElement xmlElement = _xmlDocument.CreateElement(XML_ENVELOPE);
            xmlElement.InnerText = message;
            _xmlDocument.AppendChild(xmlElement);
            X509Certificate2 certificate = base.GetCertificate(EncryptStoreName, StoreLocation);
            EncryptXml(_xmlDocument, certificate);

            return _xmlDocument;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xmlDocument"></param>
        /// <returns></returns>
        public string DecryptString(XmlDocument xmlDocument)
        {
            _xmlDocument = xmlDocument;

            DecryptXml(_xmlDocument);

            XmlElement cryptedElement = (XmlElement) _xmlDocument.SelectSingleNode("/" + XML_ENVELOPE);

            string result = cryptedElement.InnerText;

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xmlDocument"></param>
        /// <returns></returns>
        public XmlDocument SignDocument(XmlDocument xmlDocument)
        {
            X509Certificate2 certificate = base.GetCertificate(EncryptStoreName, StoreLocation);
            _xmlDocument = xmlDocument;
            SignXml(_xmlDocument, certificate);

            return _xmlDocument;
        }

        #endregion

        private static void EncryptXml(XmlDocument xmlDocument, 
            X509Certificate2 certificate)
        {
            // Extract the element to encrypt
            XmlElement elementToEncrypt = (XmlElement) xmlDocument.SelectSingleNode("/" 
                + XML_ENVELOPE);
            if (elementToEncrypt == null)
            {
                throw new XmlException(AppCommon.EX_XML_ELEMENT_NOTFOUND);
            }

            EncryptedXml encryptedXml = new EncryptedXml();

            EncryptedData encryptedData = encryptedXml.Encrypt(elementToEncrypt, 
                certificate);

            EncryptedXml.ReplaceElement(elementToEncrypt, encryptedData, 
                true);
        }

        private static void DecryptXml(XmlDocument document)
        {
            // Create a new EncryptedXml object 
            // from the document
            EncryptedXml encryptedXml = new EncryptedXml(document);
            // Decrypt the document.
            encryptedXml.DecryptDocument();
        }

        private static void SignXml(XmlDocument document, 
            X509Certificate2 certificate)
        {
            SignedXml signedXml = new SignedXml(document) { SigningKey 
                = certificate.PrivateKey };
            Reference reference = new Reference { Uri = string.Empty };

            XmlDsigC14NTransform transform = new XmlDsigC14NTransform();
            reference.AddTransform(transform);

            XmlDsigEnvelopedSignatureTransform envelope 
                = new XmlDsigEnvelopedSignatureTransform();
            reference.AddTransform(envelope);

            signedXml.AddReference(reference);

            KeyInfo keyInfo = new KeyInfo();
            keyInfo.AddClause(new KeyInfoX509Data(certificate));
            signedXml.KeyInfo = keyInfo;

            signedXml.ComputeSignature();

            XmlElement xmlDigitalSignature = signedXml.GetXml();

            document.DocumentElement.AppendChild(
                document.ImportNode(xmlDigitalSignature, true));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public bool VerifySignature()
        {
            if (_xmlDocument == null)
            {
                throw new ApplicationException(AppCommon.EX_XML_DOCUMENT_NOTDEFINED);
            }

            return this.VerifySignature(_xmlDocument);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="document"></param>
        /// <returns></returns>
        public bool VerifySignature(XmlDocument document)
        {
            SignedXml signedXml = new SignedXml(document);

            XmlNodeList nodeList = document.GetElementsByTagName(AppCommon.EX_XML_SIGNATURE);

            if (nodeList.Count <= 0)
            {
                throw new CryptographicException(AppCommon.EX_XML_SIGNATURE_NOTFOUND);
            }

            signedXml.LoadXml((XmlElement)nodeList[0]);

            return signedXml.CheckSignature();
        }
    }
}